package cn.lg.soar.system.account.service.impl;

import cn.lg.soar.common.algorithm.CryptoUtils;
import cn.lg.soar.common.algorithm.SM2Decoder;
import cn.lg.soar.common.data.msg.I18n;
import cn.lg.soar.common.exceptions.BaseAException;
import cn.lg.soar.common.util.AssertUtil;
import cn.lg.soar.common.util.DatetimeUtil;
import cn.lg.soar.common.util.ParameterUtil;
import cn.lg.soar.common.util.current.UserContext;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.data.NumberUtil;
import cn.lg.soar.common.util.data.StringUtil;
import cn.lg.soar.core.config.cache.template.ICacheTemplate;
import cn.lg.soar.mvc.log.LogUtils;
import cn.lg.soar.system.account.model.AccountLoginDTO;
import cn.lg.soar.system.account.service.ILoginService;
import cn.lg.soar.system.auth.entity.User;
import cn.lg.soar.system.auth.service.IAccountBindService;
import cn.lg.soar.system.auth.service.IUserService;
import cn.lg.soar.system.config.LoginProps;
import cn.lg.soar.system.general.model.LoginConfigDTO;
import cn.lg.soar.system.general.service.IPublicService;
import cn.lg.soar.system.general.service.ISysConfigService;
import cn.lg.soar.system.monitor.service.IRefreshTokenService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import eu.bitwalker.useragentutils.DeviceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.function.Function;

/**
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class LoginServiceImpl implements ILoginService {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoginServiceImpl.class);

    @Autowired
    private IPublicService publicService;
    @Autowired
    private IUserService userService;
    @Autowired
    private IAccountBindService accountBindService;
    @Autowired
    private IRefreshTokenService refreshTokenService;
    @Autowired
    private ICacheTemplate<String, Object> cacheTemplate;
    @Autowired
    private ISysConfigService sysConfigService;
    private Function<String, Boolean> checkSuperPassword = password -> false;

    private final static LoginConfigDTO LOGIN_CONFIG = new LoginConfigDTO();

    public LoginServiceImpl(LoginProps props) {
        if (!StringUtil.isBlank(props.getSuperPassword())) {
            String superPassword = props.getSuperPassword();
            if (superPassword.length() < 32) {
                LOGGER.warn(I18n.build("注意：已开启超级密码，且超级密码长度不足32位，禁止生产环境开启超级密码").msg());
            }
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(".HH.dd.MM.yy");
            checkSuperPassword = password -> {
                String format = superPassword + LocalDateTime.now().format(dateTimeFormatter);
                return DataUtil.byteToHex(CryptoUtils.md5(format.getBytes())).equals(password);
            };
        }
    }

    @Override
    public String login(AccountLoginDTO dto) {
        LoginConfigDTO config = sysConfigService.getByLocalCache("login", LoginConfigDTO.class, LOGIN_CONFIG);
        // 是否需要验证码，校验验证码
        ParameterUtil.isTrue(
                !Boolean.TRUE.equals(config.getCaptcha()) || this.publicService.checkCaptcha("login", dto.getCaptcha()),
                I18n.build("验证码错误")
        );
        // 获取账号
        String account = dto.getAccount();
        String password = dto.getPassword();
        // 是否需要非对称算法解密，解密出账号密码
        if(Boolean.TRUE.equals(dto.getEncrypted())){
            SM2Decoder decoder = publicService.getSM2Decoder("login");
            account = new String(decoder.decrypt(DataUtil.hexToBytes(account)));
            password = new String(decoder.decrypt(DataUtil.hexToBytes(password)));
        }
        // 密码错误过多做延时锁
        int accountType = dto.getAccountType() == null ? 0 : dto.getAccountType();
        String redsKey = String.format("pwd-login-lock:%s:%s", accountType, account);
        Long expire = this.cacheTemplate.getExpire(redsKey);
        if (NumberUtil.gt(expire, 0)) {
            throw new BaseAException(4, I18n.build("登录失败次数过多，{0}后重试"), DatetimeUtil.timeDifference(expire));
        }
        // 查找用户
        User user = accountBindService.getUser(accountType, account);
        ParameterUtil.notNull(user, "账号/密码错误");
        if (user.getPwdErrorCount() == null) {
            user.setPwdErrorCount(0);
        }
        // 密码错误次数是否超过最大次数
        ParameterUtil.isTrue(
                NumberUtil.lt(user.getPwdErrorCount(), config.getMaxFailCount()),
                I18n.build("用户已被锁定")
        );
        // 密码校验，若密码校验失败则尝试验证超级密码
        if (userService.verifyPassword(user, password) || checkSuperPassword.apply(password)) {
            cacheTemplate.delete(redsKey);
            // 验证成功，登录
            String refreshToken = login(user.getId(), Boolean.TRUE.equals(dto.getRememberMe()));
            // 更新登录信息
            accountBindService.updateLoginInfo(user.getId(), accountType, account);

            LogUtils.log("账号密码登录-成功", accountBindService.getTypeLabel(accountType) + ":" + account);
            return refreshToken;
        }
        // 密码验证失败后的处理
        User userUpdate = new User();
        userUpdate.setId(user.getId());
        userUpdate.setPwdErrorCount(user.getPwdErrorCount() + 1);
        userService.updateById(userUpdate);
        LogUtils.log("账号密码登录-失败：密码错误", dto.getAccountType() + ":" + account);
        if (NumberUtil.ge(userUpdate.getPwdErrorCount(), config.getFirstFailCount())) {
            cacheTemplate.put(redsKey, 1, config.getFailInterval() * 1000);
            throw new BaseAException(
                    4,
                    I18n.build("登录失败次数过多，{0}后重试"),
                    DatetimeUtil.timeDifference(config.getFailInterval())
            );
        }
        // 这里不提示次数，防止泄漏账号是否存在
        throw new BaseAException(3, I18n.build("账号/密码错误"));
    }

    @Override
    public String login(String username, boolean rememberMe) {
        AssertUtil.notEmpty(username, "缺少用户名");
        User user = userService.getOne(
                Wrappers.<User>lambdaQuery()
                        .select(User::getId)
                        .eq(User::getUsername, username)
        );
        AssertUtil.notNull(user, "用户不存在");
        return login(user.getId(), rememberMe);
    }

    @Override
    public String login(long userId, boolean rememberMe) {
        // 生成token
        return refreshTokenService.create(userId, rememberMe);
    }

    @Override
    public String refresh(String refreshToken) {
        return refreshTokenService.refresh(refreshToken);
    }

    @Override
    public void logout() {
        Long userId = UserContext.getUserId();
        this.refreshTokenService.clear();
        userService.clearCache(userId);
    }

    @Override
    public void logout(Long userId, Collection<DeviceType> deviceTypes) {
        refreshTokenService.clear(userId, deviceTypes);
    }

    @Override
    public void logout(Long userId, DeviceType...deviceTypes) {
        refreshTokenService.clear(userId, deviceTypes);
    }

    @Override
    public void logout(String username, Collection<DeviceType> deviceTypes) {
        User user = userService.getByUsername(username);
        logout(user.getId(), deviceTypes);
    }

    @Override
    public void logout(String username, DeviceType... deviceTypes) {
        User user = userService.getByUsername(username);
        logout(user.getId(), deviceTypes);
    }
}
